package org.voovan.tools.collection;

import java.util.AbstractMap;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * int 作为 key 的 map
 * 参考开放地址方法
 * @author helyho
 * DBase Framework.
 * WebSite: https://github.com/helyho/DBase
 * Licence: Apache v2 License
 */

public class IntKeyMap<T> {

    private static final int FREE_KEY = 0;

    public final T FREE_VALUE = null;

    private int[] keys;

    private T[] values;

    private final float fillFactor;

    private int capacity;

    private int threshold;

    private int size;

    private int mask;

    public IntKeyMap(final int size) {
       this(size, 0.75f);
    }

    public IntKeyMap(final int size, final float fillFactor) {
        if (fillFactor <= 0 || fillFactor >= 1)
            throw new IllegalArgumentException("FillFactor must be in (0, 1)");
        if (size <= 0)
            throw new IllegalArgumentException("Size must be positive!");
        final int capacity = getPowerOfTwoSize(size, fillFactor);
        mask = capacity - 1;
        this.fillFactor = fillFactor;

        this.capacity = capacity;
        keys = new int[capacity];
        values = null;
        values = (T[]) new Object[capacity];
        threshold = (int) (capacity * fillFactor);
    }

    public int getCapacity() {
        return capacity;
    }

    public boolean containsKey(int key) {
        return getReadIndex(key) > 0;
    }

    public T get(final int key) {
        if (key == FREE_KEY)
            return FREE_VALUE;

        final int index = getReadIndex(key);
        return index != -1 ? values[index] : FREE_VALUE;
    }

    public T put(final int key, final T value) {
        if (key == FREE_KEY) {
            final T ret = FREE_VALUE;
            return ret;
        }

        int index = getPutIndex(key);
        if (index < 0) {
            rehash((int) (keys.length * 3));
            index = getPutIndex(key);
        }
        final T prev = values[index];
        if (keys[index] != key) {
            keys[index] = key;
            values[index] = value;
            ++size;
            if (size >= threshold)
                rehash(keys.length * 2);
        } else {
            assert keys[index] == key;
            values[index] = value;
        }
        return prev;
    }

    public T putIfAbsent(int key, T value) {
        T t = get(key);
        if (t == null) {
            t = put(key, value);
        }
        return t;
    }

    public T remove(final int key) {
        if (key == FREE_KEY) {
            return FREE_VALUE;
        }

        int index = getReadIndex(key);
        if (index == -1)
            return FREE_VALUE;

        final T res = values[index];
        values[index] = FREE_VALUE;
        --size;
        return res;
    }

    public void clear() {
        Arrays.fill(keys, 0);
        Arrays.fill(values, null);
        size = 0;
    }

    public int size() {
        return size;
    }

    public int getKey(int index) {
        return keys[index];
    }

    public T getValue(int index) {
        return values[index];
    }

    private void rehash(final int newCapacity) {
        threshold = (int) (newCapacity * fillFactor);
        mask = newCapacity - 1;

        final int oldCapacity = keys.length;
        final int[] oldKeys = keys;
        final T[] oldValues = values;

        this.capacity = capacity;
        keys = new int[newCapacity];
        values = (T[]) new Object[newCapacity];

        for (int i = oldCapacity; i-- > 0; ) {
            if (oldKeys[i] != FREE_KEY)
                put(oldKeys[i], oldValues[i]);
        }
    }

    private int getReadIndex(final int key) {
        int index = getStartIndex(key);
        //地址已被使用
        if (keys[index] == key) {
            return index;
        }

        //地址未被使用
        if (keys[index] == FREE_KEY) {
            return -1;
        }

        //地址已被使用且 key 和期望的不同,则 index + 1 & mask, 获得下一个地址
        final int startIdx = index;
        while ((index = getNextIndex(index)) != startIdx) {
            if (keys[index] == FREE_KEY)
                return -1;
            if (keys[index] == key)
                return index;
        }
        return -1;
    }

    private int getPutIndex(final int key) {
        //地址已被使用
        final int readIdx = getReadIndex(key);
        if (readIdx >= 0) {
            return readIdx;
        }

        //地址未被使用
        final int startIdx = getStartIndex(key);
        if (keys[startIdx] == FREE_KEY) {
            return startIdx;
        }

        //地址已被使用且 key 和期望的不同,则 index + 1 & mask, 获得下一个地址
        int nextIdx = startIdx;
        while (keys[nextIdx] != FREE_KEY) {
            nextIdx = getNextIndex(nextIdx);
            if (nextIdx == startIdx)
                return -1;
        }
        return nextIdx;
    }


    private int getStartIndex(final int key) {
        return phiMix(key) & mask;
    }

    private int getNextIndex(final int currentIndex) {
        return (currentIndex + 1) & mask;
    }

    private static int nextPowerOfTwo(int x) {
        if (x == 0) return 1;
        x--;
        x |= x >> 1;
        x |= x >> 2;
        x |= x >> 4;
        x |= x >> 8;
        x |= x >> 16;
        return (x | x >> 32) + 1;
    }

    private static int getPowerOfTwoSize(final int expected, final float f) {
        final int s = Math.max(2, nextPowerOfTwo((int)Math.ceil(expected / f)));
        if (s > (1 << 30))
            throw new IllegalArgumentException("Too large (" + expected + " expected elements with load factor " + f + ")");
        return (int) s;
    }

    private static final int INT_PHI = 0x9E3779B9;

    private static int phiMix(int value) {
        int h = value * INT_PHI;
        return h ^ (h >> 16);
    }

}
